<property id="_bundlefile.1" name="jboss.distro" value="jbossas.zip"></property> <property id="_bundleconfig.1" name="listener.port" value="8080"></property>
There is a Bundles Sprint Demo available
This is the design for a basic "provisioning engine" for RHQ. We'll use the term "bundle" when talking about this new subsystem. Keep in mind the objectives to this subsystem:
Provisioning
qualify the remote machine for the pending provisioning of a specific bundle
preview the the pending provisioning of a specific bundle
push a bundle of content to remote machines ("provisioning")
Configuration
allow for provision-time configuration (tweaking the settings found in the configuration files that are associated with the bundle) ("configuration")
Update
deploy a new version of an existing bundle
identify changes to existing files
Rollback
The goal will be to support many bundle types. Each bundle type provides a different mechanism for defining and executing the bundle deployment. The initial bundle type will be the "RHQ Ant Bundle" type, using standard Ant scripting along with custom RHQ Deployment Ant Tasks. A secondary bundle type, "RHQ File Template" will also be built to ensure multiple bundle type support is robust, as well as providing a simple scripting bundle type for testing and development work.
The basic design is to allow deployment on the machine which is "at the edge". Ideally, the central server is not pushing out each command, but rather pushing out all the commands (i.e. a "recipe") and letting the agent do the real work.
This model should support:
JBoss provisioning since dropping a container and configuring it are integral to this design
Puppet recipes, Cobbler profiles and provisioning with koan will also be supportable (with some custom plugin coding)
Bundle management will be a separate RHQ subsystem. It will use the content system to store files. There is no plan to connect the configuration subsystem's representation of the file to that which is in the bundle system. Where the connection will be seen is in looking at the audit history of a resource. The user will be able to correlate when the provisioning engine ran with changes on the machine.
The design will introduce the following entities to the core RHQ model. The non-greyed out entities in the diagram below will be added:
rhq_bundle_type/BundleType: This entity defines a bundle type/format. We will initially support one primary bundle type: Ant. Additionally we will support the FileTemplate bundle type for simple scripting and testing. A Puppet bundle type is being considered. Plugins on both the Server and Agent side will check a bundle's type to determine how to parse and validate that bundle's recipe file. Each bundle type is associated with a singleton ResourceType whose ResourceComponent implements the BundleFacet, which contains the logic for checking and installing (if needed) a recipe.
rhq_bundle/Bundle: This is an umbrella entity describing the bundle content and acting as a root for the related entities. Bundles are versioned, so the real data is stored in rhq_bundle_version
rhq_bundle_version/BundleVersion: This entity is a specific instance of the bundle: the bundle distribution file, recipe and or bundle files will differ between versions. A bundle version is what is physically deployed to a destination platform/directory.
rhq_bundle_file/BundleFile: This entity is a file which is part of a bundle and is used in the deployment process. A bundle distribution file contains the recipe and 0 or more of teh referenced bundle files.
rhq_bundle_destination/BundleDestination: An entity describing to where a bundle will be deployed. It refers to a resource group of platforms and a root deploy directory.
rhq_bundle_deployment/BundleDeployment: An entity representing a physical deployment. Defines the bundle version being deployed, the destination it will be deployed to, and the configuration used for the deployment. The configuration defines the variable substitutions needed, as defined by the config_def defined for the relevant bundle_version.
rhq_bundle_res_deploy/BundleResourceDeployment: This entity represents a physical deployment of the bundle onto a given resource.
rhq_bundle_res_dep_hist/BundleResourceDeploymentHistory: This entity represents an audit trail for the deployment of the bundle onto a given resource (a platform). It will (minimally) record start and end times for deployment but will typically contain robust deployment auditing.
rhq_tagging: This entity represents a tagging relationship users can use to reflect their provisioning processes. (Best practices to be published for this?)
The following relationships are shown on this design:
(a): The resource group for a destination must contain only Platform resources as explicit group members.
(b): Each bundle will have its own repo for owning content associated with the bundle that is not pulled from an existing repo. Note, the repo in this relationship is completely independent of those described in (a).
(c): Each bundle will have its own package type. Files uploaded for the bundle will have their package types set to the bundle's package type and the package versions will be associated with the bundle's repo (see (b)).
(d): Bundles are versioned. This is the version relationship.
(e): Bundles are not hierarchical. Therefore, a user needs to be able to deploy more that one bundle to a given platform.
(f): A bundle may require that a target resource be subscribed to several repositories for content. For example, this bundle requires the machine to be subscribed to a RHEL6 repo. This relationship models that need. Only eligible platforms can be provisioned to.
To support the bundle provisioning/configuration, a new plugin subsystem will be created. The common scenarios for execution are below.
The user does development work on a bundle, and combines them all into a single archive file.
The user uploads the file and notes the "type" of bundle (and, file, etc)
The Bundle Subsystem locates a custom Bundle Parser (via a bundle server-side plugin) based on the type which will expand the archive file and create the new data structure items in the database.
A core agent engine will need to be created which will allow files which are templated with simple macros (e.g. <% rhq.ip_address %>) to be placed onto the file system with the macros expanded. Some of the these macros (like ip_address) should be facts which the engine discovers from the machine, or from the RHQ deployment. Others should be user defined. This engine will also be used by the raw config plugins, since the same feature of facts and substitution will be required when pushing one file out to a group of machines.
The following can be used as examples of macros to support:
Run facter on any Fedora 12 machine.
The following sections provide more detailed information about the design summary above.
A bundle is a named collection of content and configuration. A bundle can be versioned. Each version can contain the following bits of information:
The repositories which a machine should be subscribed to.
The type of bundle which it is (e.g. FileTemplate, Puppet, etc)
An optional recipe
The recipe content will be stored in an instance of rhq_package_version.
The recipe should not change for a bundle version - if a recipe changes, it must be considered a new bundle version - therefore, the recipe must be hardlinked to its bundle_version - the recipe content will be stored as a CLOB in the bundle_version table.
An Collection of template variables which need to be filled out by a deployment.
Stored in configuration definitions / configuration templates
A collection of default values for the template variables
A version number, and last modified date to help determine if an update needs to be run.
An md5 sum and last modified data for the recipe.
Bundle files represent the actual files which should be included in the bundle. When creating a bundle file, the user can decide to either pull in the latest version of the file, or to pull in an exact version of the file. Base on this selection, the entity will contain a reference to either a rhq_package or to a rhq_package_version.
One use case for the subsystem will be to deploy JBoss containers which are uploaded as a Zip File. This Zip File will be the rhq_bundle_file associated with the bundle. At deployment time, items in the zip file could potentially be realized with macro expansion, as the recipe dictates.
The deployment should be reusable, so the deployment is broken up into 2 entities. These are:
rhq_bundle_deploy: This entity will hold the user defined values which are required to complete the deployment. For example, the templated file has the ability to substitute in a "wiki_name" variable, then his object will contain a key-value pair which is wiki_name = "My Wiki". This is done via the rhq_config Configuration subsystem.
rhq_bundle_res_deploy: This entity provides a mapping between one deployment and many systems. For each platform where a bundle is deployed, there will be one rhq_bundle_res_deploy entity.
The Deployments, when mapped to a resource, they will need to be ordered. The user will want to ensure that certain bundles are deployed before or after others.
The Agent Plugin Container's Bundle Manager (org.rhq.core.pc.bundle.BundleManager) will support the bundle subsystem on the agent side. It will interface with those plugins that support the BundleFacet.
The Agent / Server communication will follow the standard format for RHQ subsystems. The specific APIs are below.
deploy
The server API will support the following functions:
getAllFiles: Retrieves all the files for a bundle
getFile: Returns a single file for a bundle
getFilesSince: Returns all files which have changed for a bundle since a given time or version
updateComplete: Tells the server that the update is complete
The Bundle plugin facet will need to expose the following APIs. Each will need to take in a Callback object to expose the server side calls to the plugin:
update: Determines if an update needs to occur based on the summarized bundle data, and performs the update if required.
The subsystem will need to support a model for uploading new bundles and new bundle recipes. A server side plugin should be able to parse the recipe and archive file when they are uploaded. This plugin should create/update the bundle data to be in sync with the recipe / archive.
There will be a new MANAGE_BUNDLE global permission. Users will need this permission to call any non-trivial bundle manipulation method. With this permission a user can manipulate bundles themselves (create/delete/etc) as well as deploy them to platform resources they can access through their role associations. We may or may not want to demand that the user also have MANAGE_CONTENT of the platform itself. First cut involves MANAGE_BUNDLE only, in conjunction with standard group/resource access via roles. So, a user without MANAGE_BUNDLE will not be able to perform any provisioning. With MANAGE_BUNDLE a user can provision to any accessible group of platforms. Put another way, not all bundle managers are allowed to provision to all destinations.
Scenarios to handle:
Non Bundle managers should not be able to access any bundle views.
Bundle managers should not be able to access destinations or deployments to destinations, for groups they can not view.
Bundle managers should not be able to access destinations or deployments to destinations, for groups they can not view.
Criteria methods need to prevent, or force permissions checks on, the return of group or resource-level information to which the user does not have view access.
The content system will need to support storing archive files (zip, gzip, tar, jar, war, etc) not tied to any specific resource.
See Design-Bundle Ant Tasks and Design-Bundle Ant Launcher.
The "file template" bundle type supports a easy recipe style that lets you quickly build simple bundles for deployment to your agent boxes. While the DSL supported by that file template bundle type can do many of the basic things you'd want to do when pushing out content, you may find that you need a more advanced featureset. If you need to write a recipe with more functionality than what the file template DSL can give you, you have the option to write your "recipe" as an ANT script. When you create a ANT bundle, the recipe you provide is a standard ANT script that is executed on the agent machine.
RHQ adds additional custom ANT tasks for use within your ANT scripts to help model your bundle's behavior and metadata. Read about those ANT tasks in order to properly write a RHQ Ant Bundle Recipe.
this will change in the next implementation due to design changes recently decided upon - for the real design - see Design-Bundle Ant Tasks
If you need to associate bundle files to your bundle, you do so using the standard ANT <property> tag, but to inform RHQ that you need to upload a file with your bundle, assign that property tag with a reference ID that starts with "_bundlefile.". If you need to have the user provide the values for custom configuration properties (which then get passed to your ANT script), again set a reference ID to the <property> tag but have the ID start with "_bundleconfig.". For example:
<property id="_bundlefile.1" name="jboss.distro" value="jbossas.zip"></property> <property id="_bundleconfig.1" name="listener.port" value="8080"></property>
When you create the bundle, RHQ will ask you to upload the "jbossas.zip" file. When you go to deploy the bundle (more specifically when you go to create the deployment definition), RHQ will ask you for the value of "listener.port" that you want set.
For some reason, if you file upload a ANT script as the recipe, you can't use XML notation like <property name="a" /> - you need to explicitly provide the ending tag like this: <property name="a"></property>. If you don't want to be forced into using that notation, just copy-n-paste the ANT script content directly in the text field, as opposed to using the file upload mechanism.
The Bundle contains a single script which is the set of actions or "recipe commands" to be taken on a given machine in order to deploy the Bundle. The DSL will consist of the following recipe commands:
script: Takes as a parameter a command line which is executed on the machine. This represents a script file that is explicitly included in the bundle - it is an uploaded bundle file.
command: Takes as a parameter a shell command line which is executed on the machine. The executable/shell command must already exist on the machine.
deploy: Takes 2 parameters, an archive name (-file) and a path (-directory). The archive is assumed to be one of the bundle_files. At deployment time, the archive is unzipped into the path location. The MD5 version of the file will need to be stored on the machine to ensure that the a second deployment is not done unless the version changes.
file: Takes in 2 parameters, a file name (-source) and a destination path (-destination). The file is assumed to be one of the bundle files. At deployment time, the file is copied in the specified path. If you want replacement variables to be substituted with their realized values in this file, use the realize command afterwards.
realize: Realize perform macro substitution on a file which is already on the filesystem (--file). This can be used to substitution replacement variables.
configdef: Defines one configuration definition, or "replacement variable". The name provided to this configdef will be assumed to be a replacement variable that may exist in bundle files that are to be realized. The user that deploys the bundle will need to provide a value for this config def or let the agent provide the value (by looking up the value in the agent's template engine or looking up the value as a simple Java system property).
the following recipe commands are not supported yet
service: Takes in 2 parameters, a service name and a command (start|stop|restart).
repo: Takes as a parameter a repository name so the server will be able to check that the proper subscriptions are in order prior to deploying the bundle.
package: Takes as a parameter a packge name. It will cause a specific package to be installed or updated.
An example DSL would then be:
repo -n rhel-x86_64-5 (_not supported yet_) package -n foo-1.25.rpm (_not supported yet_) package -n bar-1.25.rpm (_not supported yet_) configdef -n listener.port script foo.bash -c some parameter deploy -f jboss.tar -d <%jboss.home.directory%> realize -f <%jboss.home.directory%>/server/default/setting.xml file -s example.setting -d /etc/some/setting.ini service example restart (_not supported yet_)
There must be a server-side plugin that must be defined that can parse the different kinds of recipes that are supported. For example, a file-template server side plugin must be able to parse a file-template DSL as shown above so it can:
create the bundle version config def so a UI will know what questions to ask a user
create relationships between the bundle version and its related repos, distributions and packages
The following are Java classes and other resources that have been added to the core server and agent as part of the "bundle subsystem". This is the plumbing that now exists (in the bundle branch). All that needs to happen is to fill out the meat of these stubs to implement bundle functionality.
org.rhq.enterprise.server.bundle.BundleServerServiceImpl |
the remote endpoint that the agent talks to. In other words, when the agent sends a bundle API message to the server, this is the class that receives and processes that message. |
org.rhq.core.clientapi.server.bundle.BundleServerService |
the API interface that the agent calls. This defines the API that the agent compiles against. |
org.rhq.enterprise.server.bundle.BundleManagerBean |
the EJB3 SLSB that performs the business logic for the bundle subsystem. The BundleServerServiceImpl calls this SLSB to perform transactional db operations. |
org.rhq.enterprise.server.bundle.BundleManagerLocal |
the local EJB3 SLSB interface |
org.rhq.enterprise.server.bundle.BundleManagerRemote |
the remote EJB3 SLSB interface that is exposed as a web service and can be invoked by the remote CLI |
org.rhq.enterprise.server.plugin.pc.bundle.BundleServerPluginContainer |
the server-side plugin container that manages all bundle server side plugins (there is an associated plugin manager, but this plugin container will probably be the class that will need to be enhanced) |
org.rhq.enterprise.server.plugin.pc.bundle.* |
contains the plugin manager and validator for bundle server side plugins - the important class is the plugin container class though |
rhq/modules/enterprise/server/xml-schemas/src/main/resources/rhq-serverplugin-bundle.xsd |
the XML schema that defines what a bundle server side plugin descriptor looks like. |
The first server-side plugin implementation to support our RHQ FileTemplate bundle type:
org.rhq.enterprise.server.plugins.filetemplate.BundleServerPluginComponent |
the stateful server-side plugin component that performs the job of the plugin. This will be where the DSL is parsed, and where it determines the domain model relationships for a given bundle |
modules/enterprise/server/plugins/filetemplate-bundle/src/main/resources/META-INF/rhq-serverplugin.xml |
the filetemplate bundle server plugin descriptor |
org.rhq.core.pc.bundle.BundleManager |
the manager within the plugin container that performs tasks for the bundle subsystem. This is also the remote endpoint that the server talks to. In other words, when the server sends a bundle API message to the agent, this is the class that receives and processes that message |
org.rhq.core.clientapi.agent.bundle.BundleAgentService |
the API interface that the server calls. This defines the API that the server compiles against. |
org.rhq.core.pluginapi.bundle.BundleFacet |
The plugin facet that bundle plugins must implement. This will provide the common/generic API interface that the plugin container will call when it needs to ask a bundle plugin to process a bundle. |
org.rhq.core.clientapi.agent.bundle.* |
This package contains data transfer objects that are required as per the BundleAgentService interface. In other words, these are objects that both the server and agent need to know about and use because it is these objects that the server and agent hand off to one another when sending messages back and forth |
The first agent plugin implementation to support our RHQ FileTemplate bundle type:
org.rhq.plugins.filetemplate.FileTemplateBundlePluginServerComponent |
the stateful agent plugin component that performs the job of the plugin. It implements the BundleFacet and will process bundles of a specific bundle type |
modules/plugins/filetemplate-bundle/src/main/resources/META-INF/rhq-plugin.xml |
the agent plugin descriptor |
Bundle domain objects that match the data model described earlier can be found in modules/core/domain within the package org.rhq.core.domain.bundle. There are already some unit tests but they just test the basics.
Here are some of the APIs that we will need to implement.
BundleManagerBean should support the following API for CLI/GUI usage
RecipeParseResults verifyRecipe(String bundleTypeName, String recipe)
Bundle createBundle(String bundleName)
Creates the Repo under the covers
BundleVersion createBundleVersion(Bundle bundle, String Recipe)
Implicit package creation for each bundle file in recipe
Implicit create of config def based on recipe
Auto-increment version
List<Package> getBundleVersionPackages( BundleVersion bundleVersion )
note, maybe get this as fetch from BundleVersion criteria?
BundleFile addBundleFile( BundleVersion bundleVersion, PackageVersion pkgVersionPojo, boolean pinToPackage )
Calls content subsystem's "createPackageVersion" under the covers
void attachPackageBitsToBundleFile(BundleFile bf, byte[] bits)
void attachPackageBitsToBundleFile(BundleFile bf, InputStream bits)
Convienence method to combine the addBundleFile and attachPackageBitsToBundleFile:
BundleFile addBundleFile( BundleVersion bundleVersion, PackageVersion pkgVersion,boolean pinToPackage, byte[] bits )
BundleFile addBundleFile( BundleVersion bundleVersion, PackageVersion pkgVersion,boolean pinToPackage, InputStream bits )
BundleDeployDefinition createBundleDeployDefinition(BundleDeployDefinition def)
void deployBundle(int bundleDeployDefinitionId, int[] resourceIds)
Question: Does this generate the BundleDeployment records (the mapping table) Answer: Probably, although it may not be needed until the deployment actually starts (if it is scheduled for the future). History records hang off this and get generated on each tracked action.
List<BundleDeploymentHistory> getBundleDeploymentHistory(BundleDeploy deploy)
List<BundleDeploymentHistory> findBundleDeploymentHistoryByCriteria(BundleDeploymentHistoryCriteria bc)
bc.addFilterBundleId( id )
List<BundleDeploymentHistory> getBundleDeploymentHistory(int bundleId,int platformResourceId);
gets history for a specific bundle deployment
List<BundleDeploymentHistory> getBundleDeploymentHistory(int platformResourceId);
gets history for ALL bundles deployed on this platform
interface BundleServerPluginFacet is what all bundle server side plugins must implement. We need to fill out this interface with the API described in this section
RecipeParseResults parseRecipe(String recipe)
class RecipeParseResults needs to contain all information that can be gleened from the recipe:
ConfigurationDefinition configurationDefinitionAndTemplates
Do we need a separate ConfigurationTemplate? or can they be in ConfigurationDefinition?
List<Package> bundleFiles
interface BundleAgentService (and the impl BundleManager) needs to support the API in this section
BundleDeployResults deployBundle(BundleDeployRequest request)
request has to have things like "getBundleDeployDefinition"
all agent bundle plugins must implement the BundleFacet. This facet should support the API described in this section
BundleDeployResults deployBundle(BundleDeployRequest request)
Note: plugins never get resource IDs, nor to they typically get the detailed data that server sends to agents (for several reasons - don't forget we want the plugin dev API to be simple). Thus I think the BundleFacet probably won't be a one-to-one mapping of the BundleAgentService interface - e.g. plugin dev won't need to know about BundleDeployResults/Request but a more simple API.
The package org.rhq.core.clientapi.[server,agent].bundle will have interfaces and DTOs for agent-server comm APIs - NO agent plugin interfaces/DTOs go in here. Plugin devs do not even have access to client-api classes.
The package org.rhq.core.pluginapi.bundle has the plugin developer API such as BundleFacet and all its supporting objects. The server side does not have access to these classes - its only for agent side plugin container-plugin communication.
In the file template recipe, you can provide <% var %> replacement variables, where "var" is one of the below. For an ANT recipe, the below properties will be automatically set and can be used as a ${var} expression:
These have the values as specified by their corresponding SystemInfo API:
rhq.system.hostname |
SystemInfo.getHostname() |
rhq.system.os.name |
SystemInfo.getOperatingSystemName() |
rhq.system.os.version |
SystemInfo.getOperatingSystemVersion() |
rhq.system.os.type |
SystemInfo.getOperatingSystemType().toString() |
rhq.system.architecture |
SystemInfo.getSystemArchitecture() |
rhq.system.cpu.count |
SystemInfo.getNumberOfCpus() |
rhq.system.interfaces.java.address |
InetAddress.getByName(SystemInfo.getHostname()).getHostAddress() |
rhq.system.interfaces.<network-adapter-name>.mac |
NetworkAdapterInfo.getMacAddress() |
rhq.system.interfaces.<network-adapter-name>.type |
NetworkAdapterInfo.getType() |
rhq.system.interfaces.<network-adapter-name>.flags |
NetworkAdapterInfo.getAllFlags() |
rhq.system.interfaces.<network-adapter-name>.address |
NetworkAdapterInfo.getUnicastAddresses().get(0).getHostAddress() |
rhq.system.interfaces.<network-adapter-name>.multicast.address |
NetworkAdapterInfo.getMulticastAddresses().get(0).getHostAddress() |
These have the values of their corresponding standard Java System Property:
rhq.system.sysprop.java.io.tmpdir
rhq.system.sysprop.file.separator
rhq.system.sysprop.line.separator
rhq.system.sysprop.path.separator
rhq.system.sysprop.java.home
rhq.system.sysprop.java.version
rhq.system.sysprop.user.timezone
rhq.system.sysprop.user.region
rhq.system.sysprop.user.country
rhq.system.sysprop.user.language
If a bundle knows about repos, then guiding a bundle to a resource is an explicit subscription (rhq_repo_resource_map entry). If the engine sets this, can the core UI respect and not change it?
Can tagging be used to make this any easier?
This was Eve's concern about "make it easy to manage repos and content and bundles and packages"
There is no notion of "undeploy"
Should the plugin create resource items which are "what is installed on the machine?"
What additional work needs to be done to expose this to the CLI?
How does search fit into this?
This desgin does not have much in the way of permissions. Is that acceptable for the first release?
How do we handle updates when some data (e.g. credentials for starting/stopping jboss) are already entered in the plugin config.
The DSL seems like "puppet lite". Is that a good thing? ANSWER: yes - we are not re-writing puppet - we will in the future support a puppet integration. However, we are providing a way to perform provisioning and deployment without requiring puppet to be installed and without requiring to know how to configure/setup puppet or write the more complex puppet recipes
A "Bundle" contains 3 things:
a deployment script called a "recipe"
a group of 0, 1 or more files
a set of 0, 1 or more configuration property definitions
A "recipe" is a small piece of text that describes what a bundle contains and how it should be deployed. It could also be called a "script" - the name isn't important, but you must know that it is core element of a bundle because it defines what files and configuration property definitions belong to the bundle as well as how the bundle should be deployed.
A configuration property definition defines a named configuration setting whose value is required in order to be able to successfully deploy a bundle.
A "templatized" file is a text file that has one or more "tokens" (aka "replacement variables") that need to be replaced (aka "realized") when the file is deployed. A "token" usually represents a configuration property (see above) but it could represent agent-side "facts" (like hostname or operating system name). When a file is "realized", that means its content is re-written with all tokens replaced with their actual values. Here's a simple example:
This is a templatized file for my applications configuration file:
<my-app> <port number="<% listener.port %>" /> </my-app>
And here is that file in its realized form:
<my-app> <port number="8080" /> </my-app>